package orchestration

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	volumes3 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
	"github.com/sirupsen/logrus"
	"openstack_api/client"
	my_common "openstack_api/common"
	"time"
)

type Volume struct {
	VolumeID string `json:"volume_id,omitempty"`
}

type VolumeUpload struct {
	Volume
	Name        string `json:"name,omitempty"`
	CallBackUrl string `json:"callback_url,omitempty"`
	//MinRam      int    `json:"min_ram,omitempty"`
	//MinDisk     int    `json:"min_disk,omitempty"`
	Force      bool   `json:"force"`
	DiskFormat string `json:"disk_format"`
}

type VolumeCreate struct {
	Volume
	Name        string `json:"name"`
	CallBackUrl string `json:"callback_url"`
	ImageID     string `json:"image_id"`
	Size        int    `json:"size"`
}

func (v Volume) UploadToImage(c *gin.Context) {
	VolumeID := c.Param("uuid")
	vp := VolumeUpload{}
	vp.VolumeID = VolumeID
	err := c.ShouldBindJSON(&vp)
	if err != nil {
		v.errRet(c, 500, err.Error())
		return
	}
	volume, err := v.getVolume(VolumeID)
	if err != nil {
		v.errRet(c, 404, err.Error())
		return
	}
	vp.Force = true
	vp.DiskFormat = "qcow2"

	//if volume.Status != "available" {
	//	err := v.setVolumeStatus(volume.ID, "available")
	//	if err != nil {
	//		v.errRet(c, 500, err.Error())
	//		return
	//	}
	//}

	reciveMessage, err := v.uploadImage(volume, vp)
	if err != nil {
		v.errRet(c, 500, err.Error())
		return
	}
	//go v.ImageCallbackAndUpdate(reciveMessage.ImageID, vp.CallBackUrl, vp.MinRam, volume.Size)
	img := Image{}
	time.Sleep(time.Second * 2)
	logrus.Info(reciveMessage)
	go img.CreateCallback(vp.CallBackUrl, reciveMessage.ImageID, images.ImageStatusActive)
	image, _ := img.getImage(reciveMessage.ImageID)
	c.JSON(200, image)
	return
}

//func (v Volume) ImageCallbackAndUpdate(imageID string, callbackurl string, minram int, mindisk int) {
//	img := Image{}
//	img.CreateCallback(callbackurl, imageID, images.ImageStatusActive)
//	image, _ := img.getImage(imageID)
//	if image.Status == images.ImageStatusActive {
//		_, err := img.updateImage(imageID, ImageUpdate{
//			MinRAM:  minram,
//			MinDisk: mindisk,
//		})
//		if err != nil {
//			return
//		}
//	}
//}

func (v Volume) getVolume(volumeID string) (*volumes3.Volume, error) {
	volumeClient := client.GetVolumeClient()
	vol, err := volumes3.Get(volumeClient, volumeID).Extract()
	return vol, err
}

func (v Volume) setVolumeStatus(volumeID string, status string) error {
	volumeClient := client.GetVolumeClient()
	err := client.VolumeResetStatus(volumeClient, volumeID, client.VolumeResetStatusOptsBuilder(client.VolumeResetStatusOpts{
		Status: status,
	}))
	return err.Err
}

func (v Volume) uploadImage(volume *volumes3.Volume, vp VolumeUpload) (volumeactions.VolumeImage, error) {
	volumeClient := client.GetVolumeClient()
	upload, err := volumeactions.UploadImage(volumeClient, volume.ID, volumeactions.UploadImageOptsBuilder(volumeactions.UploadImageOpts{
		DiskFormat:      "qcow2",
		Force:           true,
		ContainerFormat: "bare",
		ImageName:       vp.Name,
	})).Extract()
	return upload, err
}

// CreateVolume 创建volume
func (v Volume) CreateVolume(c *gin.Context) {
	volumeClient := client.GetVolumeClient()
	vc := VolumeCreate{}
	err := c.ShouldBindJSON(&vc)
	if err != nil {
		v.errRet(c, 500, err.Error())
		return
	}
	img := Image{}
	image, err := img.getImage(vc.ImageID)
	if err != nil {
		v.errRet(c, 500, err.Error())
		return
	}
	if vc.Size == 0 {
		vc.Size = image.MinDiskGigabytes
	}

	ret, err := volumes3.Create(volumeClient, volumes3.CreateOptsBuilder(volumes3.CreateOpts{
		Size:    vc.Size,
		Name:    vc.Name,
		ImageID: vc.ImageID,
	})).Extract()
	if err != nil {
		v.errRet(c, 500, err.Error())
		return
	}
	if vc.CallBackUrl != "" {
		go v.CreateCallback(vc.CallBackUrl, ret.ID, "available")
	}
	c.JSON(200, ret)
	return
}

// DeleteVolume 删除volume
func (v Volume) DeleteVolume(c *gin.Context) {
	volumeClient := client.GetVolumeClient()
	VolumeID := c.Param("uuid")
	err := volumes3.Delete(volumeClient, VolumeID, volumes3.DeleteOptsBuilder(volumes3.DeleteOpts{
		Cascade: true,
	})).ExtractErr()
	if err != nil {
		v.errRet(c, 500, err.Error())
		return
	}
	c.JSON(200, nil)
	return
}

func (v Volume) errRet(c *gin.Context, code int, message interface{}) {
	c.JSON(code, gin.H{
		"code":    code,
		"message": message,
	})
}

func (v Volume) CreateCallback(callBackUrl string, id string, expectStatus string) {
	err := v.WaitForStatus(id, expectStatus, 1800)
	if err != nil {
		logrus.Error(err.Error())
	}

	vol, _ := v.getVolume(id)
	data, _ := json.Marshal(vol)
	//fmt.Println(data)

	body, statuscode, header, e := my_common.HttpPostJson(data, callBackUrl)
	if e != nil || statuscode != 200 {
		logrus.Error("Create volume, post callback failed , error=", e, ",body=", string(body), ",status_code=", statuscode, ",header=", header)
	} else {
		logrus.Info("Create volume, post callback success", ",body=", string(body), ",status_code=", statuscode, ",header=", header)
	}

}

func (v Volume) WaitForStatus(id string, status string, secs int) error {
	return gophercloud.WaitFor(secs, func() (bool, error) {
		current, err := v.getVolume(id)
		if err != nil {
			return false, err
		}

		if current.Status == status {
			return true, nil
		}

		return false, nil
	})
}
